1
//--------------------------------------------------------------------------
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 //--------------------------------------------------------------------------
10 using System
.Collections
.Concurrent
;
11 using System
.Collections
.Concurrent
.Partitioners
;
12 using System
.Collections
.Generic
;
13 using System
.Diagnostics
;
16 using System
.Runtime
.CompilerServices
;
17 using System
.Threading
;
18 using System
.Threading
.Tasks
;
19 using System
.Windows
.Forms
;
20 using Microsoft
.Drawing
;
22 namespace VisualizePartitioning
24 public partial class MainForm
: Form
26 /// <summary>Name and identifier for turning on stripe partitioning with PLINQ.</summary>
27 const string PartitioningStripe
= "Stripe";
28 /// <summary>Name and identifier for turning on hash partitioning with PLINQ.</summary>
29 const string PartitioningHash
= "Hash";
31 /// <summary>Color palette to use when rendering threads.</summary>
32 private Color
[] _colors
;
33 /// <summary>A multiplicative factor of how much work to do for each rendered line.</summary>
34 private int _workFactor
;
36 /// <summary>Provides a source of seeds for thread-local random instances.</summary>
37 private static Random _randomnessSeed
= new Random();
38 /// <summary>A thread-safe source of randomness for all threads that need random values.</summary>
39 private static ThreadLocal
<Random
> _localRandom
= new ThreadLocal
<Random
>(delegate { lock (_randomnessSeed) return new Random(_randomnessSeed.Next()); }
);
43 InitializeComponent();
45 // Configure the workloads and the color palette. The partitioning methods initialization will be done
46 // when the radio button is changed to Parallel.ForEach or PLINQ. The color palette will be
47 // initialized when the cores trackbar changes value.
48 InitializeWorkloads();
50 // Configure number of cores
52 tbCores
.Maximum
= Environment
.ProcessorCount
* 2;
53 tbCores
.Value
= Environment
.ProcessorCount
;
56 /// <summary>Initializes the color palette to use when rendering threads.</summary>
57 private void InitializeColorPalette()
59 Random random
= new Random(8); // Change seed value to change the palette used
60 _colors
= (from i
in Enumerable
.Range(0, tbCores
.Value
)
61 select Color
.FromArgb(random
.Next(128) + 127, random
.Next(128) + 127, random
.Next(128) + 127)).ToArray();
64 /// <summary>Initializes the workloads list view.</summary>
65 private void InitializeWorkloads()
67 lvWorkloads
.Items
.Clear();
68 var workloads
= new List
<Tuple
<string, Func
<int, int, int>>>();
70 // NOTE: To add a new workload, simply add a new entry below with a name and corresponding function
71 workloads
.Add(Tuple
.Create
<string, Func
<int, int, int>>("Constant", (size
, current
) => 1000 * _workFactor
));
72 workloads
.Add(Tuple
.Create
<string, Func
<int, int, int>>("Increasing Linear", (size
, current
) => 200 * current
* _workFactor
));
73 workloads
.Add(Tuple
.Create
<string, Func
<int, int, int>>("Decreasing Linear", (size
, current
) => 200 * (size
- current
) * _workFactor
));
74 workloads
.Add(Tuple
.Create
<string, Func
<int, int, int>>("Random", (size
, current
) => _localRandom
.Value
.Next(100, 10000) * _workFactor
));
76 foreach (var workload
in workloads
) lvWorkloads
.Items
.Add(new ListViewItem(workload
.Item1
) { Tag = workload }
);
77 lvWorkloads
.Items
[0].Selected
= true;
80 /// <summary>Initializes the partitioning methods list view.</summary>
81 private void InitializePartitioningMethods()
83 lvPartitioningMethods
.Items
.Clear();
84 bool usingPLINQ
= rbPLINQ
.Checked
;
85 var partitioningMethods
= new List
<Tuple
<string, Func
<int[], Partitioner
<int>>>>();
87 // Static partitioning using the Partitioner.Create overload requires static partitioner support,
88 // which Parallel.ForEach does not provide.
91 partitioningMethods
.Add(Tuple
.Create
<string, Func
<int[], Partitioner
<int>>>(
92 "Static", e
=> Partitioner
.Create(e
, false)));
95 // Add a bunch of partitioning approaches that work with both PLINQ and Parallel.ForEach
96 partitioningMethods
.Add(Tuple
.Create
<string, Func
<int[], Partitioner
<int>>>(
97 "Load Balance", e
=> Partitioner
.Create(e
, true)));
98 partitioningMethods
.Add(Tuple
.Create
<string, Func
<int[], Partitioner
<int>>>(
99 "Dynamic(1)", e
=> ChunkPartitioner
.Create(e
, 1)));
100 partitioningMethods
.Add(Tuple
.Create
<string, Func
<int[], Partitioner
<int>>>(
101 "Dynamic(16)", e
=> ChunkPartitioner
.Create(e
, 16)));
102 partitioningMethods
.Add(Tuple
.Create
<string, Func
<int[], Partitioner
<int>>>(
103 "Guided", e
=> ChunkPartitioner
.Create(e
, prev
=>
105 if (prev
<= 0) return e
.Length
<= 1 ? 1 : e
.Length
/ (Environment
.ProcessorCount
* 3);
107 return next
<= 0 ? prev
: next
;
109 partitioningMethods
.Add(Tuple
.Create
<string, Func
<int[], Partitioner
<int>>>(
110 "Grow Exponential", e
=> ChunkPartitioner
.Create(e
, prev
=> prev
<= 0 ? 1 : prev
* 2)));
111 partitioningMethods
.Add(Tuple
.Create
<string, Func
<int[], Partitioner
<int>>>(
112 "Random", e
=> ChunkPartitioner
.Create(e
, prev
=> _localRandom
.Value
.Next(e
.Length
))));
114 // Special-case some PLINQ-only hashing
117 // The actual enabling of these partitioning schemes is done later, as they can't
118 // be encoded in a partitioner but rather are based on what operators are used in the PLINQ query.
119 partitioningMethods
.Add(Tuple
.Create
<string, Func
<int[], Partitioner
<int>>>(
120 PartitioningStripe
, e
=> Partitioner
.Create(e
)));
121 partitioningMethods
.Add(Tuple
.Create
<string, Func
<int[], Partitioner
<int>>>(
122 PartitioningHash
, e
=> Partitioner
.Create(e
)));
125 // Dump the partitioners into the list view
126 foreach (var method
in partitioningMethods
) lvPartitioningMethods
.Items
.Add(new ListViewItem(method
.Item1
) { Tag = method }
);
127 lvPartitioningMethods
.Items
[0].Selected
= true;
130 /// <summary>Visualize the partitioning.</summary>
131 private void btnVisualize_Click(object sender
, EventArgs e
)
133 int numProcs
= tbCores
.Value
;
134 int width
= pbPartitionedImage
.Width
, height
= pbPartitionedImage
.Height
;
135 bool useParallelFor
= rbParallelFor
.Checked
, useParallelForEach
= rbParallelForEach
.Checked
;
136 _workFactor
= tbWorkFactor
.Value
;
138 // If we're using Parallel.ForEach or PLINQ, ensure a partitioning scheme was selected and use it
139 Tuple
<string, Func
<int[], Partitioner
<int>>> selectedMethod
= null;
142 if (lvPartitioningMethods
.SelectedIndices
.Count
== 0) return;
143 else selectedMethod
= (Tuple
<string, Func
<int[], Partitioner
<int>>>)lvPartitioningMethods
.SelectedItems
[0].Tag
;
146 // Make sure a workload was selected and use it
147 if (lvWorkloads
.SelectedItems
.Count
== 0) return;
148 var selectedWorkload
= (Tuple
<string, Func
<int, int, int>>)lvWorkloads
.SelectedItems
[0].Tag
;
150 // Create a new Bitmap to store the rendered output
151 var bmp
= new Bitmap(width
, height
);
153 // Disable the start button and kick off the background work
154 btnVisualize
.Enabled
= false;
155 Task
.Factory
.StartNew(() =>
157 int nextId
= -1; // assign each thread a unique id
158 var threadId
= new ThreadLocal
<int>(() => Interlocked
.Increment(ref nextId
));
160 using (FastBitmap fastBmp
= new FastBitmap(bmp
)) // get faster access to the Bitmap's contents
162 var sw
= Stopwatch
.StartNew(); // time the operation
165 Parallel
.For(0, height
, new ParallelOptions { MaxDegreeOfParallelism = numProcs }
, i
=>
167 int id
= threadId
.Value
;
168 DoWork(selectedWorkload
.Item2(height
, i
));
169 for (int j
= 0; j
< width
; j
++) fastBmp
.SetColor(j
, i
, _colors
[id
% _colors
.Length
]);
174 // Create the partitioner to be used
175 var partitioner
= selectedMethod
.Item2(Enumerable
.Range(0, height
).ToArray());
177 if (useParallelForEach
)
179 // Run the work with Parallel.ForEach
180 Parallel
.ForEach(partitioner
, new ParallelOptions { MaxDegreeOfParallelism = numProcs }
, i
=>
182 int id
= threadId
.Value
;
183 DoWork(selectedWorkload
.Item2(height
, i
));
184 for (int j
= 0; j
< width
; j
++) fastBmp
.SetColor(j
, i
, _colors
[id
% _colors
.Length
]);
189 // Run the work with PLINQ. If a special partitioning method was selected, use relevant query operators
190 // to get PLINQ to use that partitioning approach.
191 var source
= partitioner
.AsParallel().WithDegreeOfParallelism(numProcs
);
192 if (selectedMethod
.Item1
== PartitioningStripe
) source
= source
.TakeWhile(elem
=> true);
193 else if (selectedMethod
.Item1
== PartitioningHash
) source
= source
.Join(Enumerable
.Range(0, height
).AsParallel(), i
=> i
, i
=> i
, (i
, ignore
) => i
);
196 int id
= threadId
.Value
;
197 DoWork(selectedWorkload
.Item2(height
, i
));
198 for (int j
= 0; j
< width
; j
++) fastBmp
.SetColor(j
, i
, _colors
[id
% _colors
.Length
]);
203 // Return the total time from the task
207 // When the work completes, run the following on the UI thread
210 // Dispose of the old image (if there was one) and display the new one
211 var old
= pbPartitionedImage
.Image
;
212 pbPartitionedImage
.Image
= bmp
;
213 if (old
!= null) old
.Dispose();
215 // Re-enable controls on the form and display the elapsed time
216 btnVisualize
.Enabled
= true;
217 lblTime
.Text
= "Time: " + t
.Result
.ToString();
218 }, TaskScheduler
.FromCurrentSynchronizationContext());
221 /// <summary>Does an amount of work relative to the amount requested.</summary>
222 /// <param name="workAmount">The amount of work to perform.</param>
223 [MethodImpl(MethodImplOptions
.NoOptimization
| MethodImplOptions
.NoInlining
)]
224 private static int DoWork(int workAmount
)
227 for (int i
= 0; i
< workAmount
; i
++) value *= workAmount
;
231 /// <summary>Update relevant portions of the form when the API radio buttons are checked.</summary>
232 /// <param name="sender">The radio button.</param>
233 /// <param name="e">The event args.</param>
234 private void rbAPI_CheckedChanged(object sender
, EventArgs e
)
236 lvPartitioningMethods
.Enabled
= !rbParallelFor
.Checked
;
237 lvPartitioningMethods
.HideSelection
= !lvPartitioningMethods
.Enabled
;
239 // Recreate partitioning methods every time a radio button is checked,
240 // as which API is selected determines which partitioning methods are available
241 InitializePartitioningMethods();
244 private void tbCores_ValueChanged(object sender
, EventArgs e
)
246 toolTip1
.SetToolTip(tbCores
, tbCores
.Value
.ToString());
247 InitializeColorPalette();
249 ThreadPool
.GetMinThreads(out worker
, out io
);
250 ThreadPool
.SetMinThreads(tbCores
.Value
, io
);
253 private void tbWorkFactor_ValueChanged(object sender
, EventArgs e
)
255 toolTip1
.SetToolTip(tbWorkFactor
, tbWorkFactor
.Value
.ToString());